package com.atom.app.mvvm.ui.activities

import android.animation.ValueAnimator
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.*
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import com.atom.app.mvvm.BR
import com.atom.app.mvvm.R
import com.atom.app.mvvm.VMFactory
import com.atom.app.mvvm.camera.*
import com.atom.app.mvvm.databinding.ActivityCameraPhotoBinding
import com.atom.module.logger.Logger
import com.atom.module.mvvm.base.view.activity.AbstractBaseMVVMActivity
import com.atom.module.mvvm.base.view.fragment.AbstractBaseMVVMFragment
import com.atom.module.mvvm.base.viewmodel.AbstractBaseViewModel
import com.atom.module.mvvm.base.model.EmptyModel
import com.atom.module.permissions.AutoPermissions
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.otaliastudios.cameraview.*
import com.otaliastudios.cameraview.controls.Facing
import com.otaliastudios.cameraview.controls.Mode
import com.otaliastudios.cameraview.controls.Preview
import com.otaliastudios.cameraview.filter.Filters
import com.otaliastudios.cameraview.filter.MultiFilter
import com.otaliastudios.cameraview.filters.DuotoneFilter
import com.otaliastudios.cameraview.frame.Frame
import com.otaliastudios.cameraview.frame.FrameProcessor
import java.io.ByteArrayOutputStream
import java.io.File

class CameraActivity : AbstractBaseMVVMActivity<ActivityCameraPhotoBinding, CameraViewModel>(),
    OptionView.Callback {
    override fun initContentView(savedInstanceState: Bundle?): Int {
        return R.layout.activity_camera_photo
    }

    override fun initViewModel(): CameraViewModel {
        return ViewModelProvider(this, VMFactory).get(CameraViewModel::class.java).also {
            Logger.e("CameraFragment", it.hashCode())
        }
    }

    override fun initVariableId(): Int {
        return BR.cameraViewModel
    }

    private val camera: CameraView by lazy { binding.camera }
    private val controlPanel: ViewGroup by lazy { binding.controls }
    private var captureTime: Long = 0
    private var currentFilter = 0
    private val allFilters = Filters.values()

    override fun initViewBinding(binding: ActivityCameraPhotoBinding, viewModel: CameraViewModel) {
        super.initViewBinding(binding, viewModel)
        CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE)
        camera.setLifecycleOwner(this)
        camera.addCameraListener(Listener())
        camera.addFrameProcessor(object : FrameProcessor {
            private var lastTime = System.currentTimeMillis()
            override fun process(frame: Frame) {
                val newTime = frame.time
                val delay = newTime - lastTime
                lastTime = newTime
                Logger.v("Frame delayMillis:", delay, "FPS:", 1000 / delay)
                if (false) {
                    if (frame.format == ImageFormat.NV21
                        && frame.dataClass == ByteArray::class.java
                    ) {
                        val data = frame.getData<ByteArray>()
                        val yuvImage = YuvImage(
                            data,
                            frame.format,
                            frame.size.width,
                            frame.size.height,
                            null
                        )
                        val jpegStream = ByteArrayOutputStream()
                        yuvImage.compressToJpeg(
                            Rect(
                                0, 0,
                                frame.size.width,
                                frame.size.height
                            ), 100, jpegStream
                        )
                        val jpegByteArray = jpegStream.toByteArray()
                        val bitmap = BitmapFactory.decodeByteArray(
                            jpegByteArray,
                            0, jpegByteArray.size
                        )
                        bitmap.toString()
                    }
                }
            }
        })
        binding.edit.setOnClickListener { edit() }
        binding.capturePicture.setOnClickListener { capturePicture() }
        binding.capturePictureSnapshot.setOnClickListener { capturePictureSnapshot() }
        binding.captureVideo.setOnClickListener { captureVideo() }
        binding.captureVideoSnapshot.setOnClickListener { captureVideoSnapshot() }
        binding.toggleCamera.setOnClickListener { toggleCamera() }
        binding.changeFilter.setOnClickListener { changeCurrentFilter() }
        val group = controlPanel.getChildAt(0) as ViewGroup
        val watermark = findViewById<View>(R.id.watermark)
        val options: List<Option<*>> = listOf(
            // Layout
            Option.Width(), Option.Height(),
            // Engine and preview
            Option.Mode(), Option.Engine(), Option.Preview(),
            // Some controls
            Option.Flash(), Option.WhiteBalance(), Option.Hdr(),
            Option.PictureMetering(), Option.PictureSnapshotMetering(),
            Option.PictureFormat(),
            // Video recording
            Option.PreviewFrameRate(), Option.VideoCodec(), Option.Audio(), Option.AudioCodec(),
            // Gestures
            Option.Pinch(), Option.HorizontalScroll(), Option.VerticalScroll(),
            Option.Tap(), Option.LongTap(),
            // Watermarks
            Option.OverlayInPreview(watermark),
            Option.OverlayInPictureSnapshot(watermark),
            Option.OverlayInVideoSnapshot(watermark),
            // Frame Processing
            Option.FrameProcessingFormat(),
            // Other
            Option.Grid(), Option.GridColor(), Option.UseDeviceOrientation()
        )
        val dividers = listOf(
            // Layout
            false, true,
            // Engine and preview
            false, false, true,
            // Some controls
            false, false, false, false, false, true,
            // Video recording
            false, false, false, true,
            // Gestures
            false, false, false, false, true,
            // Watermarks
            false, false, true,
            // Frame Processing
            true,
            // Other
            false, false, true
        )
        for (i in options.indices) {
            val view = OptionView<Any>(this)
            view.setOption(options[i] as Option<Any>, this)
            view.setHasDivider(dividers[i])
            group.addView(
                view,
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
        }
        controlPanel.viewTreeObserver.addOnGlobalLayoutListener {
            BottomSheetBehavior.from(controlPanel).state = BottomSheetBehavior.STATE_HIDDEN
        }

        // Animate the watermark just to show we record the animation in video snapshots
        val animator = ValueAnimator.ofFloat(1f, 0.8f)
        animator.duration = 300
        animator.repeatCount = ValueAnimator.INFINITE
        animator.repeatMode = ValueAnimator.REVERSE
        animator.addUpdateListener { animation ->
            val scale = animation.animatedValue as Float
            watermark.scaleX = scale
            watermark.scaleY = scale
            watermark.rotation = watermark.rotation + 2
        }
        animator.start()
    }

    override fun initData(view: View) {
        super.initData(view)

    }

    private inner class Listener : CameraListener() {
        override fun onCameraOpened(options: CameraOptions) {
            val group = controlPanel.getChildAt(0) as ViewGroup
            for (i in 0 until group.childCount) {
                val view = group.getChildAt(i) as OptionView<*>
                view.onCameraOpened(camera, options)
            }
        }

        override fun onCameraError(exception: CameraException) {
            super.onCameraError(exception)
            Logger.e("Got CameraException #" + exception.reason)
        }

        override fun onPictureTaken(result: PictureResult) {
            super.onPictureTaken(result)
            if (camera.isTakingVideo) {
                Logger.e("Captured while taking video. Size=" + result.size)
                return
            }
            // This can happen if picture was taken with a gesture.
            val callbackTime = System.currentTimeMillis()
            if (captureTime == 0L) captureTime = callbackTime - 300
            Logger.e(
                "onPictureTaken called! Launching activity. Delay:",
                callbackTime - captureTime
            )
            PicturePreviewActivity.pictureResult = result
            val intent = Intent(this@CameraActivity, PicturePreviewActivity::class.java)
            intent.putExtra("delay", callbackTime - captureTime)
            startActivity(intent)
            captureTime = 0
            Logger.e("onPictureTaken called! Launched activity.")
        }

        override fun onVideoTaken(result: VideoResult) {
            super.onVideoTaken(result)
            Logger.w("onVideoTaken called! Launching activity.")
            VideoPreviewActivity.videoResult = result
            val intent = Intent(this@CameraActivity, VideoPreviewActivity::class.java)
            startActivity(intent)
            Logger.w("onVideoTaken called! Launched activity.")
        }

        override fun onVideoRecordingStart() {
            super.onVideoRecordingStart()
            Logger.w("onVideoRecordingStart!")
        }

        override fun onVideoRecordingEnd() {
            super.onVideoRecordingEnd()
            Logger.w("Video taken. Processing...", "onVideoRecordingEnd!")
        }

        override fun onExposureCorrectionChanged(
            newValue: Float,
            bounds: FloatArray,
            fingers: Array<PointF>?
        ) {
            super.onExposureCorrectionChanged(newValue, bounds, fingers)
            Logger.w("Exposure correction:$newValue")
        }

        override fun onZoomChanged(newValue: Float, bounds: FloatArray, fingers: Array<PointF>?) {
            super.onZoomChanged(newValue, bounds, fingers)
            Logger.w("Zoom:$newValue")
        }
    }

    override fun onBackPressed() {
        val b = BottomSheetBehavior.from(controlPanel)
        if (b.state != BottomSheetBehavior.STATE_HIDDEN) {
            b.state = BottomSheetBehavior.STATE_HIDDEN
            return
        }
        super.onBackPressed()
    }

    private fun edit() {
        BottomSheetBehavior.from(controlPanel).state = BottomSheetBehavior.STATE_COLLAPSED
    }

    private fun capturePicture() {
        if (camera.mode == Mode.VIDEO) return run {
            Logger.e("Can't take HQ pictures while in VIDEO mode.", false)
        }
        if (camera.isTakingPicture) return
        captureTime = System.currentTimeMillis()
        Logger.e("Capturing picture...")
        camera.takePicture()
    }

    private fun capturePictureSnapshot() {
        if (camera.isTakingPicture) return
        if (camera.preview != Preview.GL_SURFACE) return run {
            Logger.e("Picture snapshots are only allowed with the GL_SURFACE preview.", true)
        }
        captureTime = System.currentTimeMillis()
        Logger.e("Capturing picture snapshot...", false)
        camera.takePictureSnapshot()
    }

    private fun captureVideo() {
        if (camera.mode == Mode.PICTURE) return run {
            Logger.e("Can't record HQ videos while in PICTURE mode.", false)
        }
        if (camera.isTakingPicture || camera.isTakingVideo) return
        Logger.e("Recording for 5 seconds...", true)
        camera.takeVideo(File(filesDir, "video.mp4"), 5000)
    }

    private fun captureVideoSnapshot() {
        if (camera.isTakingVideo) return run {
            Logger.e("Already taking video.", false)
        }
        if (camera.preview != Preview.GL_SURFACE) return run {
            Logger.e("Video snapshots are only allowed with the GL_SURFACE preview.", true)
        }
        Logger.e("Recording snapshot for 5 seconds...", true)
        camera.takeVideoSnapshot(File(filesDir, "video.mp4"), 5000)
    }

    private fun toggleCamera() {
        if (camera.isTakingPicture || camera.isTakingVideo) return
        when (camera.toggleFacing()) {
            Facing.BACK -> Logger.e("Switched to back camera!", false)
            Facing.FRONT -> Logger.e("Switched to front camera!", false)
        }
    }

    private fun changeCurrentFilter() {
        if (camera.preview != Preview.GL_SURFACE) return run {
            Logger.e("Filters are supported only when preview is Preview.GL_SURFACE.", true)
        }
        if (currentFilter < allFilters.size - 1) {
            currentFilter++
        } else {
            currentFilter = 0
        }
        val filter = allFilters[currentFilter]
        Logger.e(filter.toString(), false)

        // Normal behavior:
        camera.filter = filter.newInstance()

        // To test MultiFilter:
        val duotone = DuotoneFilter();
        duotone.firstColor = Color.RED;
        duotone.secondColor = Color.GREEN;
        camera.filter = MultiFilter(duotone, filter.newInstance());
    }

    override fun <T : Any> onValueChanged(option: Option<T>, value: T, name: String): Boolean {
        if (option is Option.Width || option is Option.Height) {
            val preview = camera.preview
            val wrapContent = value as Int == ViewGroup.LayoutParams.WRAP_CONTENT
            if (preview == Preview.SURFACE && !wrapContent) {
                Logger.e(
                    "The SurfaceView preview does not support width or height changes. " +
                        "The view will act as WRAP_CONTENT by default.", true
                )
                return false
            }
        }
        option.set(camera, value)
        BottomSheetBehavior.from(controlPanel).state = BottomSheetBehavior.STATE_HIDDEN
        Logger.e("Changed " + option.name + " to " + name, false)
        return true
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        AutoPermissions.parsePermissions(this, requestCode, permissions)
        val valid = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
        if (valid && !camera.isOpened) {
            camera.open()
        }
    }
}

class CameraFragment : AbstractBaseMVVMFragment<ActivityCameraPhotoBinding, CameraViewModel>() {
    override fun initContentView(
        inflater: LayoutInflater?,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): Int {
        return R.layout.activity_camera_photo
    }

    override fun initViewModel(): CameraViewModel {
        return ViewModelProvider(requireActivity(), VMFactory).get(CameraViewModel::class.java)
            .also {
                Logger.e("CameraFragment", it.hashCode())
            }
    }

    override fun initVariableId(): Int {
        return BR.cameraViewModel
    }
}

class CameraViewModel : AbstractBaseViewModel<EmptyModel>(EmptyModel)
